#include <cstring>
#include <thread>
#include <chrono>
#include "Util/util.h"
#include "Util/logc.h"
#include "VEncoder.h"

namespace Media{

     VEncoder::VEncoder(int channel, const VEncoderConfig& config){
        m_channel = channel;
        m_config = config;
        m_frameConsumerIndex = 0;
        m_running = false;
     }

    VEncoder::~VEncoder(){
        Stop();
    }

    bool VEncoder::Start(){
        if(m_running) return true;

        m_running = true;
        if(init() && m_encoder){
            m_encoder->start(m_encoder);
        }

        std::thread th([this]{
            packetProc();
        });
        th.detach();

        return true;
    }
    bool VEncoder::Stop(){
        if(m_running){
            m_running = false;
            std::unique_lock<std::mutex> ulo(m_cvMux);
            m_cv.wait(ulo);
            m_encoder->stop(m_encoder);
            m_encoder = nullptr;
        }
        return true;
    }

    void VEncoder::packetProc(){
        while(m_running){
            VideoEncPacket encPacket;
            VideoEncBuf	buffer;
            FrameCommon::CodecId encType; 

            memset(&encPacket, 0, sizeof(encPacket));
            encPacket.vBuffer = &buffer;
            encPacket.vSize = 1;

            while(m_encoder->getPacket(m_encoder, &encPacket) == 0 && m_running){
                // tracef("VEncoder  packet coming ");
                char *   ptr    = (char *)(encPacket.vBuffer[0].buffer);
                size_t   size   = encPacket.vBuffer[0].len;
                char *   pext   = (char *)encPacket.specBuffer;
                size_t   exsize = encPacket.specLength;
                uint64_t pts    = encPacket.pts;
                bool     key    = (encPacket.type == videoEncFrameI)?true:false;

                if (encPacket.encType == videoEncH264)
                {
                    encType = FrameCommon::H264;
                }
                else if(encPacket.encType == videoEncH265)
                {
                    encType = FrameCommon::H265;
                }
                if(pts == 0){
                    pts = Fdt::getCurrentMillisecond();
                }

                auto framePtr = FrameCommon::Frame::CreateFrame(encType, ptr, size, pts, pts, key, 0);
                auto func_map = m_frameConsumers;

                for(auto &func : func_map){
                    (func.second)(framePtr);
                }

                m_encoder->releasePacket(m_encoder);
//                std::this_thread::sleep_for(std::chrono::milliseconds(1));
            }
            std::this_thread::sleep_for(std::chrono::milliseconds(1));
        }

        m_cv.notify_one();
    }

    // bool VEncoder::InputAVFrame(AVFrame::Ptr frame){
    //     m_encoder->putFrame(m_encoder, frame->GetData());

    //     return false;
    // }

    bool VEncoder::InputAVFrame(void* frame){
        if(m_encoder->putFrame(m_encoder, frame) < 0){
            errorf("vencoder putFrame fail");
        }

        return true;
    }

    VEncoder::FrameConsumerId VEncoder::AddConsumer(std::function<void (const FrameCommon::Frame::Ptr&)> consumer){
        std::lock_guard<std::recursive_mutex> guard(m_mux);
        m_frameConsumerIndex++;
        m_frameConsumers[m_frameConsumerIndex] = consumer;
        return m_frameConsumerIndex;
    }

    bool VEncoder::RemoveConsumer(FrameConsumerId id){
        std::lock_guard<std::recursive_mutex> guard(m_mux);
        if(m_frameConsumers.find(id) != m_frameConsumers.end()){
            m_frameConsumers.erase(id);
            return true;
        }
        
        return false;
    }

    int VEncoder::GetChannel(){
        return m_channel;
    }

    bool VEncoder::init(){
        VideoEncDesc encDesc;
        memset(&encDesc, 0, sizeof(encDesc));
        encDesc.card = 0;
        encDesc.srcs = 1;
        encDesc.channel = m_channel;
        if(createVideoEncoder(&encDesc, &m_encoder) == 0){
            VideoEncFormat encFormat;
            memset(&encFormat, 0, sizeof(VideoEncFormat));
            encFormat.type = videoEncH264; //videoEncH265
            encFormat.width = m_config.m_width;
            encFormat.height = m_config.m_height;
            encFormat.bitrateControl = videoEncBitrateCtrlConstant;  
            encFormat.fps = 30;
            encFormat.gop = encFormat.fps*2;
            encFormat.bitrate = (1*encFormat.width*encFormat.height/1024)*encFormat.fps/30;
            encFormat.quality = 1;
            encFormat.qualityType = 0;
            encFormat.pack = videoEncPackTypeRAW;
            encFormat.profile = h264ProfileHigh;
            m_encoder->setFormat(m_encoder, &encFormat);
            return true;
        }
        return false;
    }

}